/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.explorer;
import java.awt.Component;
import java.beans.*;
import java.io.Externalizable;
import java.io.IOException;
import java.io.Serializable;
import java.io.ObjectInputValidation;
import java.util.*;
import org.openide.util.datatransfer.*;
import org.openide.TopManager;
import org.openide.util.*;
import org.openide.nodes.*;
/** Manages a selection and root context for a (set of) Explorer view(s).
* The views should register their {@link java.beans.VetoableChangeListener}s and {@link java.beans.PropertyChangeListener}s at
* the <code>ExplorerManager</code> of the Explorer they belong to. The manager listens on changes
* to the node hierarchy and updates the selection and root node.
*
* <P>
* Deserialization of this object is done with validation with priority 10. In readObject
* the ExplorerManager is temporarily initialized with void values. These values are
* replaced with original ones when the validation proceeds.
*
* @author Ian Formanek, Petr Hamernik, Jaroslav Tulach, Jan Jancura
*/
public final class ExplorerManager extends Object
implements Serializable, Cloneable {
/** generated Serialized Version UID */
static final long serialVersionUID = -4330330689803575792L;
/** Name of property for the root context. */
public static final String PROP_ROOT_CONTEXT = "rootContext"; // NOI18N
/** Name of property for the explored context. */
public static final String PROP_EXPLORED_CONTEXT = "exploredContext"; // NOI18N
/** Name of property for the node selection. */
public static final String PROP_SELECTED_NODES = "selectedNodes"; // NOI18N
/** The support for VetoableChangeEvent */
private transient VetoableChangeSupport vetoableSupport;
/** The support for PropertyChangeEvent */
private transient PropertyChangeSupport propertySupport;
/** The current root context */
private Node rootContext;
/** The current explored context */
private Node exploredContext;
/** The currently selected beans */
private Node[] selectedNodes;
/** listener to destroy of root node */
private transient Listener listener;
/** weak listener */
private transient NodeListener weakListener;
/** The explorer's resource bundle */
static java.util.ResourceBundle explorerBundle = NbBundle.getBundle (ExplorerManager.class);
/** Construct a new manager. */
public ExplorerManager () {
init ();
}
/** Initializes the nodes.
*/
private void init () {
exploredContext = rootContext = Node.EMPTY;
selectedNodes = new Node[0];
listener = new Listener ();
weakListener = WeakListener.node (listener, null);
}
/** Clones the manager.
* @return manager with the same settings like this one
*/
public Object clone () {
ExplorerManager em = new ExplorerManager ();
em.rootContext = rootContext;
em.exploredContext = exploredContext;
em.selectedNodes = selectedNodes;
return em;
}
/** Get the set of selected nodes.
* @return the selected nodes; empty (not <code>null</code>) if none are selected
*/
public Node[] getSelectedNodes () {
return selectedNodes;
}
/** Set the set of selected nodes.
* @param value the nodes to select; empty (not <code>null</code>) if none are to be selected
* @exception PropertyVetoException when the given nodes cannot be selected
*/
public final void setSelectedNodes (Node[] value) throws PropertyVetoException {
if (Arrays.equals (value, selectedNodes)) {
return;
}
if (value.length != 0 && vetoableSupport != null) {
// we send the vetoable change event only for non-empty selections
vetoableSupport.fireVetoableChange(PROP_SELECTED_NODES, selectedNodes, value);
}
Node[] oldValue = selectedNodes;
selectedNodes = value;
if (propertySupport != null) {
propertySupport.firePropertyChange(PROP_SELECTED_NODES, oldValue, selectedNodes);
}
}
/** Get the explored context.
* @return the node being explored, or <code>null</code>
*/
public final Node getExploredContext() {
return exploredContext;
}
/** Set the explored context.
* @param value the new node to explore, or <code>null</code> if none should be explored. The action is ignored if the node is above the current root context in the node hierarchy.
*/
public final void setExploredContext(Node value) {
if ((exploredContext != null) && (exploredContext.equals(value))) return;
if (!isUnderRoot(value)) return; // invalid new exploredContext - above root
try {
setSelectedNodes(new Node[0]);
} catch (PropertyVetoException e) {
throw new InternalError(explorerBundle.getString("ERR_MustNotVetoEmptySelection"));
}
Node oldValue = exploredContext;
exploredContext = value;
if (propertySupport != null)
propertySupport.firePropertyChange(PROP_EXPLORED_CONTEXT, oldValue, exploredContext);
}
/** Get the root context.
* @return the root context node
*/
public final Node getRootContext() {
return rootContext;
}
/** Set the root context.
* @param value the new node to serve as a root
*/
public final void setRootContext(Node value) {
if ((rootContext != null) && (rootContext.equals (value))) return;
Node oldValue = rootContext;
rootContext = value;
oldValue.removeNodeListener (weakListener);
rootContext.addNodeListener (weakListener);
if (propertySupport != null)
propertySupport.firePropertyChange(PROP_ROOT_CONTEXT, oldValue, rootContext);
setExploredContext(rootContext);
}
/** Add a <code>PropertyChangeListener</code> to the listener list.
* @param l the listener to add
*/
public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
if (propertySupport == null)
propertySupport = new PropertyChangeSupport(this);
propertySupport.addPropertyChangeListener(l);
}
/** Remove a <code>PropertyChangeListener</code> from the listener list.
* @param l the listener to remove
*/
public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
if (propertySupport != null)
propertySupport.removePropertyChangeListener(l);
}
/** Add a <code>VetoableListener</code> to the listener list.
* @param l the listener to add
*/
public synchronized void addVetoableChangeListener(VetoableChangeListener l) {
if (vetoableSupport == null)
vetoableSupport = new VetoableChangeSupport(this);
vetoableSupport.addVetoableChangeListener(l);
}
/** Remove a <code>VetoableChangeListener</code> from the listener list.
* @param l the listener to remove
*/
public synchronized void removeVetoableChangeListener(VetoableChangeListener l) {
if (vetoableSupport != null)
vetoableSupport.removeVetoableChangeListener(l);
}
/** Checks whether given Node is a subnode of rootContext.
* @return true if specified Node is under current rootContext
*/
private boolean isUnderRoot(Node node) {
while (node != null) {
if (node.equals(rootContext)) return true;
node = node.getParentNode();
}
return false;
}
/** serializes object
*/
private void writeObject (java.io.ObjectOutputStream os)
throws java.io.IOException {
Node root = NodeOp.findRoot(rootContext);
Node.Handle rootHandle = root.getHandle();
//System.out.println("Root: " + root.getName()); // NOI18N
os.writeObject(rootHandle);
if (rootHandle != null) {
os.writeObject(NodeOp.createPath(rootContext, root));
os.writeObject(NodeOp.createPath(exploredContext, root));
for (int i = selectedNodes.length; --i >= 0;) {
os.writeObject(NodeOp.createPath(selectedNodes[i], root));
}
os.writeObject(null);
}
}
/** Deserializes the view and initializes it
*
*/
private void readObject(java.io.ObjectInputStream ois)
throws java.io.IOException, ClassNotFoundException {
// perform initialization
init();
// read root handle
Node.Handle h = (Node.Handle) ois.readObject();
ObjectInputValidation oiv;
if (h == null) {
oiv = new MyValidation ();
} else {
String[] rootCtx = (String[])ois.readObject();
String[] exploredCtx = (String[])ois.readObject ();
LinkedList ll = new LinkedList ();
for (;;) {
String[] path = (String[])ois.readObject();
if (path == null) break;
ll.add(path);
};
oiv = new MyValidation (h.getNode (), rootCtx, exploredCtx, ll);
}
ois.registerValidation (oiv, 10);
}
/** Find the proper Explorer manager for a given component.
* This is done by traversing the component hierarchy and
* finding the first ancestor that implements {@link Provider}.
* <P>
* This method should be used in {@link Component#addNotify} of each component
* that works with the Explorer manager, e.g.:
* <p><CODE><pre>
* private transient ExplorerManager explorer;
*
* public void addNotify () {
* super.addNotify ();
* explorer = ExplorerManager.find (this);
* }
* </pre></CODE>
*
* @param comp component to find the manager for
* @return the manager, or a new empty manager if no ancestor implements <code>Provider</code>
*
* @see Provider
*/
public static ExplorerManager find (Component comp) {
// start looking for manager from parent, not the component itself
for (;;) {
comp = comp.getParent ();
if (comp == null) {
// create new explorer because nothing has been found
return new ExplorerManager ();
}
if (comp instanceof Provider) {
// ok, found a provider, return its manager
return ((Provider)comp).getExplorerManager ();
}
}
}
/** Finds node by given path */
static Node findPath(Node r, String[] path) {
try {
return NodeOp.findPath(r, path);
} catch (NodeNotFoundException ex) {
return ex.getClosestNode();
}
}
//
// inner classes
//
/** Interface for components wishing to provide their own <code>ExplorerManager</code>.
* @see ExplorerManager#find
*/
public static interface Provider {
/** Get the explorer manager.
* @return the manager
*/
public ExplorerManager getExplorerManager ();
}
/** Listener to be notified when root node has been destroyed.
* Then the root node is changed to Node.EMPTY
*/
private class Listener extends NodeAdapter {
/** Fired when the node is deleted.
* @param ev event describing the node
*/
public void nodeDestroyed(NodeEvent ev) {
if (ev.getNode ().equals (getRootContext ())) {
// node has been deleted
setRootContext (Node.EMPTY);
}
}
}
/** Validation after readObject */
private final class MyValidation
implements java.io.ObjectInputValidation, Runnable {
Node root;
String[] rootCtx;
String[] exploredCtx;
List selNodes;
public MyValidation(Node r, String[] rc, String[] e, List list) {
root = r;
rootCtx = rc;
exploredCtx = e;
selNodes = list;
}
public MyValidation() {
}
public void validateObject() {
synchronized (ExplorerManager.this) {
if (rootCtx == null) {
Node rep = TopManager.getDefault().getPlaces().nodes().repository();
setRootContext(rep);
setExploredContext(rep);
return;
}
}
run ();
// posts request to be processed lazily
// RequestProcessor.postRequest (this);
}
public void run () {
synchronized (ExplorerManager.this) {
setRootContext(findPath(root, rootCtx));
setExploredContext(findPath(root, exploredCtx));
try {
// converts list of String[] to Node
ListIterator it = selNodes.listIterator ();
while (it.hasNext ()) {
String[] path = (String[])it.next ();
it.set (findPath(root, path));
}
setSelectedNodes((Node[]) selNodes.toArray(new Node[selNodes.size ()]));
} catch (PropertyVetoException ex) {
}
}
}
}
}
/*
* Log
* 17 Gandalf 1.16 1/13/00 Ian Formanek NOI18N
* 16 Gandalf 1.15 1/12/00 Ian Formanek NOI18N
* 15 Gandalf 1.14 11/5/99 Jaroslav Tulach WeakListener has now
* registration methods.
* 14 Gandalf 1.13 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 13 Gandalf 1.12 8/27/99 Jaroslav Tulach New threading model &
* Children.
* 12 Gandalf 1.11 8/13/99 Jaroslav Tulach Description.
* 11 Gandalf 1.10 8/13/99 Jaroslav Tulach New Main Explorer
* 10 Gandalf 1.9 8/12/99 David Simonek deserialization fix
* 9 Gandalf 1.8 8/3/99 Jan Jancura System.out.. cleared
* 8 Gandalf 1.7 7/30/99 David Simonek serialization fixes
* 7 Gandalf 1.6 7/21/99 David Simonek properties switcher
* fixed
* 6 Gandalf 1.5 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 5 Gandalf 1.4 5/16/99 Jaroslav Tulach Serializes the selection
* in the explorer panel.
* 4 Gandalf 1.3 3/22/99 Jesse Glick [JavaDoc]
* 3 Gandalf 1.2 3/20/99 Jesse Glick [JavaDoc]
* 2 Gandalf 1.1 3/4/99 Jan Jancura Localization moved
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
* Beta Change History:
* 0 Tuborg 0.20 --/--/98 anonymous added Window parameter to the constructor
* 0 Tuborg 0.30 --/--/98 anonymous added copy, cut, paste performers - not fully implemented
* 0 Tuborg 0.32 --/--/98 anonymous added default values for rootContext, exploredContext and selectedBeans
* 0 Tuborg 0.33 --/--/98 Petr Hamernik clipboard package moved...
* 0 Tuborg 0.35 --/--/98 Petr Hamernik many changes with clipboard
* 0 Tuborg 0.36 --/--/98 Petr Hamernik [Petr, jst] clipboard
* 0 Tuborg 0.37 --/--/98 Petr Hamernik small change
* 0 Tuborg 0.38 --/--/98 Petr Hamernik temporary solution of setTitle to ExplorerFrame
* 0 Tuborg 0.39 --/--/98 Jan Formanek setRootContext sets the exploredContext to the new rootContext
* 0 Tuborg 0.39 --/--/98 Jan Formanek as a side effect
* 0 Tuborg 0.40 --/--/98 Petr Hamernik setSelectedBeans doesn't do anything, when new Value equals old one (sel. beans).
* 0 Tuborg 0.41 --/--/98 Ales Novak made serializable
* 0 Tuborg 0.42 --/--/98 Jan Formanek lazy initialization of property and vetoable support (due to serialization)
* 0 Tuborg 0.44 --/--/98 Jan Formanek everything made transient
* 0 Tuborg 0.45 --/--/98 Jan Formanek initializes rootContext, ... with empty context after deserialization
* 0 Tuborg 0.50 --/--/98 Jan Formanek SWITCH TO NODES
* 0 Tuborg 0.51 --/--/98 Jan Jancura getDisplayName
* 0 Tuborg 0.52 --/--/98 Petr Hamernik Bug fixes...
* 0 Tuborg 0.53 --/--/98 Jan Formanek got rid of Explorer.getExplorerBundle
* 0 Tuborg 0.54 --/--/98 Jan Formanek added default constructor
* 0 Tuborg 0.55 --/--/98 Jan Formanek added checking whether new exploredContext in setExploredContext
* 0 Tuborg 0.55 --/--/98 Jan Formanek is under rootContext
* 0 Tuborg 0.56 --/--/98 Ales Novak rootContext, selectedBeans, exploredContext are serialized
* 0 Tuborg 0.60 --/--/98 Jan Formanek property name constants, nodeTracker and title setting removed
* 0 Tuborg 0.61 --/--/98 Jaroslav Tulach if rootContext is not serialized then traverses to parent
*/